/*
 * Copyright 2011 myTDev.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.mytdev.util.properties;

import com.mytdev.util.properties.formatters.Formatter;
import com.mytdev.util.properties.formatters.FormatterProvider;
import com.mytdev.util.properties.formatters.impl.DefaultFormatterProvider;
import com.mytdev.util.properties.formatters.impl.EnumFormatterProvider;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.io.File;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URL;
import java.text.MessageFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Locale;
import java.util.regex.Pattern;

/**
 *
 * @author Yann D'Isanto
 */
public final class FormattedProperties {

    /**
     * The default formatter provider.
     */
    public static final FormatterProvider DEFAULT_FORMATTER_PROVIDER = DefaultFormatterProvider.getDefaultFormatterProvider();

    /**
     * The default enum formatter provider.
     */
    public static final EnumFormatterProvider ENUM_FORMATTER_PROVIDER = new EnumFormatterProvider();

    /**
     * The formatter provider.
     */
    private FormatterProvider formatterProvider = DEFAULT_FORMATTER_PROVIDER;

    /**
     * The properties reader.
     */
    private PropertiesAccessor propertiesGetter;

    /**
     * Creates a properties parser using the specified properties reader.
     *
     * @param propertiesGetter the properties reader used to retreive
     * properties.
     */
    public FormattedProperties(PropertiesAccessor propertiesGetter) {
        this.propertiesGetter = propertiesGetter;
    }

    /**
     * Returns the properties reader used to retreive the properties.
     *
     * @return a PropertiesAccessor instance.
     */
    public PropertiesAccessor getPropertiesGetter() {
        return propertiesGetter;
    }

    /**
     * Sets the specified PropertiesAccessor as the properties reader used to
     * retreive the properties.
     *
     * @param propertiesGetter the PropertiesAccessor to use to retreive the
     * properties. The null value is accepted and so all properties retreival
     * will return null.
     */
    public void setPropertiesGetter(PropertiesAccessor propertiesGetter) {
        this.propertiesGetter = propertiesGetter;
    }

    /**
     * Returns the formatter provider used to parse properties.
     *
     * @return a FormatterProvider instance.
     */
    public FormatterProvider getFormatterProvider() {
        return formatterProvider;
    }

    /**
     * Sets the formatter provider that should be used to parse properties.
     *
     * @param formatterProvider a FormatterProvider instance. The null value is
     * not allowed.
     */
    public void setFormatterProvider(FormatterProvider formatterProvider) {
        if (formatterProvider == null) {
            throw new NullPointerException("formatterProvider is null");
        }
        this.formatterProvider = formatterProvider;
    }

    /**
     * Returns the formatter associated with the specified type.
     *
     * @param type the data type.
     * @return a Formatter or null if no formatter is associated with the
     * specified type.
     */
    @SuppressWarnings("unchecked")
    private <T> Formatter<T> getFormatter(Class<T> type) {
        Formatter<T> formatter = formatterProvider.getFormatter(type);
        if (formatter == null && Enum.class.isAssignableFrom(type)) {
            formatter = ENUM_FORMATTER_PROVIDER.getFormatter(type);
        }
        if (formatter == null && type.getSuperclass() != null) {
            formatter = (Formatter<T>) getFormatter(type.getSuperclass());
        }
        return formatter;
    }

    /**
     * Retreives the volue of the specified property as the specified type.
     *
     * @param key the property key.
     * @param type the type of the property.
     * @return the property value or null if not found.
     * @throws IllegalArgumentException if the specified type is not supported
     * or if an error occurs while parsing the property.
     */
    public <T> T get(String key, Class<T> type) throws IllegalArgumentException {
        Formatter<T> formatter = getFormatter(type);
        if (formatter == null) {
            throw new IllegalArgumentException("unsupported type: " + type.getName());
        }
        String value = null;
        if (propertiesGetter != null) {
            value = propertiesGetter.getProperty(key);
        }
        if (value == null) {
            return null;
        }
        try {
            return formatter.parse(value);
        } catch (ParseException ex) {
            throw new IllegalArgumentException(ex.getLocalizedMessage(), ex);
        }
    }

    /**
     * Returns the specified property value as a String. If args are specified,
     * the property string value is formatted using MessageFormat. Returns null
     * if the property is not found.
     *
     * @param key the property key.
     * @param args optionnal arguments to format the property string value.
     * @return the property value as a String or null if not found.
     */
    public String get(String key, Object... args) {
        return get(key, null, args);
    }

    /**
     * Returns the specified property value as a String. If args are specified,
     * the property string value is formatted using MessageFormat. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @param defaultValue the default value.
     * @param args optionnal arguments to format the property string value.
     * @return the property value as a String.
     */
    public String get(String key, String defaultValue, Object... args) {
        String value = get(key, String.class);
        if (value != null && args.length > 0) {
            value = MessageFormat.format(value, args);
        }
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a BigDecimal.
     *
     * @param key the property key.
     * @return the property value as a BigDecimal or null if not found.
     */
    public BigDecimal getBigDecimal(String key) {
        return getBigDecimal(key, null);
    }

    /**
     * Returns the specified property value as a BigDecimal. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a BigDecimal.
     */
    public BigDecimal getBigDecimal(String key, BigDecimal defaultValue) {
        BigDecimal value = get(key, BigDecimal.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a BigInteger.
     *
     * @param key the property key.
     * @return the property value as a BigInteger or null if not found.
     */
    public BigInteger getBigInteger(String key) {
        return getBigInteger(key, null);
    }

    /**
     * Returns the specified property value as a BigInteger. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a BigInteger.
     */
    public BigInteger getBigInteger(String key, BigInteger defaultValue) {
        BigInteger value = get(key, BigInteger.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Boolean.
     *
     * @param key the property key.
     * @return the property value as a Boolean or null if not found.
     */
    public Boolean getBoolean(String key) {
        return getBoolean(key, null);
    }

    /**
     * Returns the specified property value as a Boolean. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Boolean.
     */
    public Boolean getBoolean(String key, Boolean defaultValue) {
        Boolean value = get(key, Boolean.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Byte.
     *
     * @param key the property key.
     * @return the property value as a Byte or null if not found.
     */
    public Byte getByte(String key) {
        return getByte(key, null);
    }

    /**
     * Returns the specified property value as a Byte. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Byte.
     */
    public Byte getByte(String key, Byte defaultValue) {
        Byte value = get(key, Byte.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Character.
     *
     * @param key the property key.
     * @return the property value as a Character or null if not found.
     */
    public Character getChar(String key) {
        return getChar(key, null);
    }

    /**
     * Returns the specified property value as a Character. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Character.
     */
    public Character getChar(String key, Character defaultValue) {
        Character value = get(key, Character.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Color.
     *
     * @param key the property key.
     * @return the property value as a Color or null if not found.
     */
    public Color getColor(String key) {
        return getColor(key, null);
    }

    /**
     * Returns the specified property value as a Color. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Color.
     */
    public Color getColor(String key, Color defaultValue) {
        Color value = get(key, Color.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Dimension.
     *
     * @param key the property key.
     * @return the property value as a Dimension or null if not found.
     */
    public Dimension getDimension(String key) {
        return getDimension(key, null);
    }

    /**
     * Returns the specified property value as a Dimension. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Dimension.
     */
    public Dimension getDimension(String key, Dimension defaultValue) {
        Dimension value = get(key, Dimension.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Double.
     *
     * @param key the property key.
     * @return the property value as a Double or null if not found.
     */
    public Double getDouble(String key) {
        return getDouble(key, null);
    }

    /**
     * Returns the specified property value as a Double. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Double.
     */
    public Double getDouble(String key, Double defaultValue) {
        Double value = get(key, Double.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a File.
     *
     * @param key the property key.
     * @return the property value as a File or null if not found.
     */
    public File getFile(String key) {
        return getFile(key, null);
    }

    /**
     * Returns the specified property value as a File. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a File.
     */
    public File getFile(String key, File defaultValue) {
        File value = get(key, File.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Float.
     *
     * @param key the property key.
     * @return the property value as a Float or null if not found.
     */
    public Float getFloat(String key) {
        return getFloat(key, null);
    }

    /**
     * Returns the specified property value as a Float. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Float.
     */
    public Float getFloat(String key, Float defaultValue) {
        Float value = get(key, Float.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Font.
     *
     * @param key the property key.
     * @return the property value as a Font or null if not found.
     */
    public Font getFont(String key) {
        return getFont(key, null);
    }

    /**
     * Returns the specified property value as a Font. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Font.
     */
    public Font getFont(String key, Font defaultValue) {
        Font value = get(key, Font.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as an InetAddress.
     *
     * @param key the property key.
     * @return the property value as an InetAddress or null if not found.
     */
    public InetAddress getInetAddress(String key) {
        return getInetAddress(key, null);
    }

    /**
     * Returns the specified property value as an InetAddress. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as an InetAddress.
     */
    public InetAddress getInetAddress(String key, InetAddress defaultValue) {
        InetAddress value = get(key, InetAddress.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as an Integer.
     *
     * @param key the property key.
     * @return the property value as an Integer or null if not found.
     */
    public Integer getInt(String key) {
        return getInt(key, null);
    }

    /**
     * Returns the specified property value as an Integer. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as an Integer.
     */
    public Integer getInt(String key, Integer defaultValue) {
        Integer value = get(key, Integer.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Locale.
     *
     * @param key the property key.
     * @return the property value as a Locale or null if not found.
     */
    public Locale getLocale(String key) {
        return getLocale(key, null);
    }

    /**
     * Returns the specified property value as a Locale. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Locale.
     */
    public Locale getLocale(String key, Locale defaultValue) {
        Locale value = get(key, Locale.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Long.
     *
     * @param key the property key.
     * @return the property value as a Long or null if not found.
     */
    public Long getLong(String key) {
        return getLong(key, null);
    }

    /**
     * Returns the specified property value as a Long. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Long.
     */
    public Long getLong(String key, Long defaultValue) {
        Long value = get(key, Long.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Pattern.
     *
     * @param key the property key.
     * @return the property value as a Pattern or null if not found.
     */
    public Pattern getPattern(String key) {
        return getPattern(key, null);
    }

    /**
     * Returns the specified property value as a Pattern. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Pattern.
     */
    public Pattern getPattern(String key, Pattern defaultValue) {
        Pattern value = get(key, Pattern.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a Short.
     *
     * @param key the property key.
     * @return the property value as a Short or null if not found.
     */
    public Short getShort(String key) {
        return getShort(key, null);
    }

    /**
     * Returns the specified property value as a Short. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a Short.
     */
    public Short getShort(String key, Short defaultValue) {
        Short value = get(key, Short.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as a SimpleDateFormat.
     *
     * @param key the property key.
     * @return the property value as a SimpleDateFormat or null if not found.
     */
    public SimpleDateFormat getSimpleDateFormat(String key) {
        return getSimpleDateFormat(key, null);
    }

    /**
     * Returns the specified property value as a SimpleDateFormat. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as a SimpleDateFormat.
     */
    public SimpleDateFormat getSimpleDateFormat(String key, SimpleDateFormat defaultValue) {
        SimpleDateFormat value = get(key, SimpleDateFormat.class);
        return value != null ? value : defaultValue;
    }

    /**
     * Returns the specified property value as an URL.
     *
     * @param key the property key.
     * @return the property value as an URL or null if not found.
     */
    public URL getURL(String key) {
        return getURL(key, null);
    }

    /**
     * Returns the specified property value as an URL. Returns the
     * specified default value if the property is not found.
     *
     * @param key the property key.
     * @return the property value as an URL.
     */
    public URL getURL(String key, URL defaultValue) {
        URL value = get(key, URL.class);
        return value != null ? value : defaultValue;
    }
}
